<?php
namespace Kuoqi\Worker;

use Kuoqi\Worker\Logs;
#stream_context_create
#Sockets

/**
 * 基于WebSocket的socket 关于http的socket部分，使用这个作为最基础的定义栏目信息进行
 * Class WebSocket
 * @package Kuoqi\Worker
 * User kuoqi
 * Datetime 2022/3/29 20:18
 * Remark
 */
class WebSocket extends Socket
{

    /**
     * pidfile
     * @var string
     * User kuoqi
     * Datetime 2022/3/30 19:07
     * Remark
     */
    public $pidfile = '';

    /**
     * 绑定事件信息，就看需不需要握手就是了
     * @var array
     * User kuoqi
     * Datetime 2022/3/24 9:06
     * Remark
     */
    protected $events = array(
        'connect'=>null,//链接自定义执行函数
        'disconnect'=>null,//链接自定义登出函数
        'handshake'=>null,//握手协议
        'message'=>null,//接收到信息的绑定
        'command'=>null,//自定义事件发生器
    );//链接事件发生器列表

    public function __construct($address, $port)
    {
        parent::__construct($address, $port);
        $clsname = str_replace('.','',$address).$port.'.php';
        $this->pidfile = '/var/run/kuoqi_websocket_'.$clsname.'.pid';
        //获取基础的绑定信息
        $this->say($this->pidfile);
        $this->say(sprintf('listen %s on %s',$address,$port));

    }

    /**
     * 检测PID信息
     * @return bool
     * User kuoqi
     * Datetime 2022/3/31 10:05
     * Remark
     */
    private function checkPid(){
        clearstatcache(true,$this->pidfile);
        return file_exists($this->pidfile);
    }

    /**
     * 绑定监听方法
     * @param $onFuncname
     * @param $onEvent
     * User kuoqi
     * Datetime 2022/3/24 9:27
     * Remark
     */
    public function on($onFuncname,$onEvent){
        if(in_array($onFuncname,array_keys($this->events))){
            $this->events[$onFuncname] = $onEvent;
        }else{
            $this->say('Socket Error: there is no Bind code with ['.$onFuncname.']');
        }
    }

    /**
     * 重新实现监听方法
     * User kuoqi
     * Datetime 2022/3/31 13:43
     * Remark
     */
    public function listen(){
        $socketArr = $this->sockets;
        $write = NULL;
        $except = NULL;

        //自动选择来消息的socket 如果是握手 自动选择主机
        //int socket_select ( array &$read , array &$write , array &$except , int $tv_sec [, int $tv_usec = 0 ] )
        //无线阻塞
        if(false ===@socket_select($socketArr, $write, $except, 0)){
            $this->say("socket_select() failed, reason: " . socket_strerror(socket_last_error()));
        }
        foreach ($socketArr as $k=>$socket){
            if ($socket == $this->master){
                //如果是关于主机的链接
                $client = socket_accept($this->master);
                //准备链接的时候已经断了，这个不管
                if ($client < 0){
                    $this->say("socket_accept() failed");
                    continue;
                }

                //执行链接客户端的操作
                $this->doConnect($client);
            }else{
                //如果是关于客户端的一些链接发送信息类
                $bytes = socket_recv($socket,$buffer,2048,0);
                if ($bytes == 0){
                    $this->doDisconnect($socket);//这个客户端突然发送一些为空的数据，标识自动断开了，GG
                }else{
                    //登录,$key为对应序号
                    //$key = array_search($socket, $this->sockets);
                    if($this->events['message']){
                        $rs = $this->events['message'];
                        //返回flase的情况会自动执行对接函数信息
                        $rs($socket, $this->decode($buffer),$this);
                    }else{
                        $this->say('need to message try $this->on("message",FUNCTION)');
                    }
                }
            }
        }
    }

    /**
     * 执行开始和循环的地方，这个要考虑将本信息发送给守护进程，进行守护进程监测
     * User kuoqi
     * Datetime 2022/3/31 15:05
     * Remark
     */
    function run(){
        while(true){
            $this->listen();
        }
    }

    /**
     * 创建接入，需要返回前端信息，否则前端一直pendding 关于http/https的接入处理
     * @param $socket
     * User kuoqi
     * Datetime 2022/3/31 13:47
     * Remark
     */
    function doConnect($socket){
        $bytes = socket_recv($socket,$buffer,2048,0);
        if($bytes==0){
            $this->say("该用户未发送任何验证数据");
        }else{
            $header = $this->getHeaders($buffer);
            list($resource, $host, $origin, $key) = $header;
            $reskey = $this->calcKey($key);
            $upgrade
                = "HTTP/1.1 101 Switching Protocol\r\n" .
                "token:XXX\r\n".
                "Upgrade: websocket\r\n" .
                "Connection: Upgrade\r\n" .
                "Sec-WebSocket-Accept: " . $reskey . "\r\n\r\n";
            array_push($this->sockets, $socket);
            socket_write($socket, $upgrade, strlen($upgrade));
            $this->say("\n" . $socket . " CONNECTED! on ".date("Y-n-d H:i:s"));
        }
    }

    /**
     * 创建解除
     * @param $socket
     * User kuoqi
     * Datetime 2022/3/31 13:50
     * Remark
     */
    function doDisconnect($socket){
        $index = array_search($socket, $this->sockets);
        socket_close($socket);
        $this->say($socket . " DISCONNECTED!");
        if ($index >= 0){
            unset($this->sockets[$index]);
        }
    }

    //死的时候
    public function __destruct()
    {
        parent::__destruct(); // TODO: Change the autogenerated stub
        $this->say("i'm die");
    }
}

